// Package torrent 提供种子文件的生成、编码、解码和校验功能
// 支持目录扫描、文件哈希计算、进度回调等特性
// 使用blake3哈希算法和gzip压缩来优化性能和存储空间
package torrent

import (
	"bytes"
	"errors"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/vmihailenco/msgpack/v5"
	"github.com/zeebo/blake3"
)

// EncodeTorrent msgpack编码种子文件结构，返回二进制数据
func (m *torrentManagerImpl) EncodeTorrent(t *TorrentMeta) ([]byte, error) {
	// 使用msgpack序列化种子元数据
	encoded, err := msgpack.Marshal(t)
	if err != nil {
		return nil, err
	}

	// 压缩序列化后的数据以减小文件大小
	return m.Compress(encoded)
}

// DecodeTorrent 从msgpack二进制数据解码种子文件结构
func (m *torrentManagerImpl) DecodeTorrent(data []byte) (*TorrentMeta, error) {
	// 解压数据
	compressed, err := m.Decompress(data)
	if err != nil {
		log.Printf("解压种子数据失败: %v", err)
		return nil, err
	}
	var t TorrentMeta
	// 反序列化种子元数据
	err = msgpack.Unmarshal(compressed, &t)
	if err != nil {
		log.Printf("解码种子文件失败: %v", err)
		return nil, err
	}
	// 基础校验：PieceLength 必须为正整数
	if t.PieceLength <= 0 {
		log.Printf("种子元数据中的块大小无效: %d", t.PieceLength)
		return nil, errors.New("种子元数据中的块大小无效")
	}
	return &t, nil
}

// SaveTorrent 保存种子文件到指定路径
func (m *torrentManagerImpl) SaveTorrent(t *TorrentMeta, savePath string) error {
	// 编码种子文件结构
	encoded, err := m.EncodeTorrent(t)
	if err != nil {
		log.Printf("编码种子文件失败: %v", err)
		return err
	}

	// 保存种子文件到磁盘
	if err = os.WriteFile(savePath, encoded, 0644); err != nil {
		log.Printf("写入种子文件失败: %v", err)
		return err
	}
	return nil
}

// LoadTorrent 从指定路径加载种子文件
func (m *torrentManagerImpl) LoadTorrent(savePath string) (*TorrentMeta, error) {
	// 读取种子文件
	encoded, err := os.ReadFile(savePath)
	if err != nil {
		log.Printf("读取种子文件失败: %v", err)
		return nil, err
	}

	// 解码种子文件结构
	torrentMeta, err := m.DecodeTorrent(encoded)
	if err != nil {
		return nil, err
	}
	return torrentMeta, nil
}

// GenerateTorrent 生成种子文件（不带进度回调）
func (m *torrentManagerImpl) GenerateTorrent(dir string, pieceLength int) (*TorrentMeta, error) {
	return m.GenerateTorrentWithProgress(dir, pieceLength, nil)
}

// GenerateTorrentWithProgress 通过通道上报进度的种子生成函数
func (m *torrentManagerImpl) GenerateTorrentWithProgress(dir string, pieceLength int, progressCh chan<- float64) (*TorrentMeta, error) {
	if progressCh != nil {
		// 失败也要关闭，避免读端泄漏；成功会在返回前发送100%
		defer close(progressCh)
	}

	// 验证块大小参数
	if pieceLength <= 0 {
		log.Printf("无效的块大小: %d", pieceLength)
		return nil, errors.New("无效的块大小")
	}

	// 获取目录的绝对路径
	absDir, err := filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	// 取目录名（不包含路径）作为种子根路径
	rootName := filepath.Base(absDir)
	if rootName == "." || rootName == string(filepath.Separator) || strings.TrimSpace(rootName) == "" {
		return nil, errors.New("根路径的目录名无效")
	}

	// 调用内部函数生成文件列表，忽略绝对路径，使用相对根目录路径
	files, err := m.walkFilesForTorrentWithProgress(absDir, pieceLength, progressCh)
	if err != nil {
		return nil, err
	}

	// 计算总字节数
	var totalSize int64
	for _, f := range files {
		totalSize += f.Length
	}

	// 使用blake3哈希算法计算info哈希
	hasher := blake3.New()
	// 按扫描顺序计算info哈希（基于每个文件的分块哈希）
	for _, f := range files {
		for _, bh := range f.PieceHashes {
			_, _ = hasher.Write(bh[:])
		}
	}
	var infoHash Hash
	copy(infoHash[:], hasher.Sum(nil))

	// 构建种子元数据
	meta := &TorrentMeta{
		RootPath:    rootName, // 只保存目录名
		Files:       files,
		InfoHash:    infoHash,
		PieceLength: pieceLength,
		TotalLength: totalSize,
	}

	// 成功结束时确保发送100%进度
	if progressCh != nil {
		progressCh <- 1.0
	}
	return meta, nil
}

// VerifyTorrent 修改，传入上级目录 parentDir，拼接 RootPath 后扫描校验
func (m *torrentManagerImpl) VerifyTorrent(t *TorrentMeta, parentDir string) ([]string, error) {
	result, err := m.VerifyTorrentWithProgress(t, parentDir, nil)
	if err != nil {
		return nil, err
	}
	return result.MissingFiles, nil
}

// VerifyTorrentWithProgress 通过通道上报进度的种子校验函数（重建元数据对比）
func (m *torrentManagerImpl) VerifyTorrentWithProgress(t *TorrentMeta, parentDir string, progressCh chan<- float64) (*VerifyResult, error) {
	if progressCh != nil {
		defer close(progressCh)
	}

	// 验证种子元数据的根路径
	if t.RootPath == "" {
		return nil, errors.New("种子元数据的根路径为空")
	}

	// 解析种子文件中的块大小，使用该块大小生成新的元数据
	rootPath := filepath.Join(parentDir, t.RootPath)

	// 将生成进度转发到外部 progressCh
	var genProgress chan float64
	var forwardDone chan struct{}
	if progressCh != nil {
		genProgress = make(chan float64, 1)
		forwardDone = make(chan struct{})
		// 启动协程转发进度信息
		go func() {
			for p := range genProgress {
				select {
				case progressCh <- p:
				default:
				}
			}
			close(forwardDone)
		}()
	}

	// 重新生成目标目录的种子元数据用于对比
	newMeta, err := m.GenerateTorrentWithProgress(rootPath, t.PieceLength, genProgress)
	if genProgress != nil && forwardDone != nil {
		<-forwardDone
	}
	if err != nil {
		return nil, err
	}

	// 比较原始种子元数据和新生成的元数据
	result := m.compareTorrentMetas(t, newMeta)

	// 发送100%完成进度
	if progressCh != nil {
		select {
		case progressCh <- 1.0:
		default:
		}
	}
	return result, nil
}

// compareTorrentMetas 比较两个种子元数据，返回差异（缺失和多余文件）
func (m *torrentManagerImpl) compareTorrentMetas(expected, actual *TorrentMeta) *VerifyResult {
	res := &VerifyResult{MissingFiles: []string{}, ExtraFiles: []string{}}

	// 构建映射：路径字符串 -> FileInfo
	expectedMap := make(map[string]FileInfo)
	for _, f := range expected.Files {
		pathStr := filepath.Join(f.Path...)
		expectedMap[pathStr] = f
	}

	actualMap := make(map[string]FileInfo)
	for _, f := range actual.Files {
		pathStr := filepath.Join(f.Path...)
		actualMap[pathStr] = f
		// 统计多余文件（实际存在但种子中不存在的文件）
		if _, ok := expectedMap[pathStr]; !ok {
			res.ExtraFiles = append(res.ExtraFiles, pathStr)
		}
	}

	// 检查缺失或内容不匹配的文件
	for pathStr, ef := range expectedMap {
		af, ok := actualMap[pathStr]
		if !ok {
			// 文件不存在
			res.MissingFiles = append(res.MissingFiles, pathStr)
			continue
		}
		// 检查文件大小和哈希是否匹配
		if ef.Length != af.Length || !m.equalPieceHashes(ef.PieceHashes, af.PieceHashes) {
			res.MissingFiles = append(res.MissingFiles, pathStr)
		}
	}
	return res
}

// equalPieceHashes 比较分块哈希是否一致
func (m *torrentManagerImpl) equalPieceHashes(a, b []Hash) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if !m.EqualHash(a[i], b[i]) {
			return false
		}
	}
	return true
}

// EqualHash 比较两个哈希值是否相等
func (m *torrentManagerImpl) EqualHash(a, b Hash) bool {
	// 由于是[32]byte的封装，所以调用bytes.Equal无法直接进行比较，应该在调用的时候转换为[]byte
	return bytes.Equal(a[:], b[:])
}
